package com.sunrise.xdoc.view.operator;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.annotation.PostConstruct;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ManagedProperty;
import javax.faces.bean.SessionScoped;

import org.apache.commons.lang3.BooleanUtils;
import org.primefaces.model.DefaultTreeNode;
import org.primefaces.model.TreeNode;

import com.sunrise.xdoc.entity.operator.Function;
import com.sunrise.xdoc.entity.operator.Module;
import com.sunrise.xdoc.entity.operator.OperatorProfile;
import com.sunrise.xdoc.entity.operator.Privilege;
import com.sunrise.xdoc.service.IOperatorService;

/**
 * The table to modify the privileges of operatorProfile
 * 
 * @author Phan Hồng Phúc
 * 
 */
@ManagedBean
@SessionScoped
public class PrivilegeTable implements Serializable {
	private static final long serialVersionUID = 5977843866671894387L;

	@ManagedProperty(value = "#{operatorService}")
	private IOperatorService operatorService;

	private Map<Module, List<Function>> module2Functions;
	private List<Privilege> privileges;
	/**
	 * Map of functionId to Function (the map contains all the functions in the
	 * system)
	 */
	private Map<Long, Function> functionId2Funtion = new HashMap<>();
	private Map<Long, DefaultTreeNode> functionId2FuntionNode = new HashMap<>();

	/**
	 * Map of functionId to privilege (the map just contains the privileges of
	 * the operatorProfile)
	 */
	private Map<Long, Privilege> functionId2Privilege = new HashMap<>();

	private OperatorProfile operatorProfile;

	private DefaultTreeNode root;

	@PostConstruct
	public void init() {
		module2Functions = operatorService.getMapModule2Functions();

		root = new DefaultTreeNode("root", null);
		for (Module m : module2Functions.keySet()) {
			PrivilegeElementRow mRow = new PrivilegeElementRow(m.getId(), m.getName(), true);
			DefaultTreeNode mNode = new DefaultTreeNode(mRow, root);
			mNode.setExpanded(true);
			for (Function f : module2Functions.get(m)) {
				PrivilegeElementRow fRow = new PrivilegeElementRow(f.getId(), f.getName(), false);
				if (f.getPossibleMode().contains("C")) {
					fRow.setCreate(false);
				}
				if (f.getPossibleMode().contains("R")) {
					fRow.setRead(false);
				}
				if (f.getPossibleMode().contains("U")) {
					fRow.setUpdate(false);
				}
				if (f.getPossibleMode().contains("D")) {
					fRow.setDelete(false);
				}
				if (f.getPossibleMode().contains("E")) {
					fRow.setExecute(false);
				}

				DefaultTreeNode fNode = new DefaultTreeNode(fRow, mNode);
				functionId2FuntionNode.put(f.getId(), fNode);
				functionId2Funtion.put(f.getId(), f);
			}
		}
	}

	/**
	 * Save and update privileges.
	 */
	public void saveUpdate() {
		operatorService.saveOrUpdatePrivileges(privileges);
	}

	/**
	 * Delete all privileges of the current operatorProfile
	 */
	public void deleteAll() {
		operatorService.deletePrivileges(privileges);
	}

	public void editPrivilege(OperatorProfile operatorProfile) {
		this.operatorProfile = operatorProfile;
		if (operatorProfile.getId() != null) {
			privileges = operatorService.getPrivilegeByOperatorProfileId(operatorProfile.getId());
		} else {
			privileges = new ArrayList<>();
		}
		functionId2Privilege.clear();
		// Reset table data
		for (DefaultTreeNode fRow : functionId2FuntionNode.values()) {
			((PrivilegeElementRow) fRow.getData()).resetRowData();
		}

		// Fill privilege mode data to the rows
		for (Privilege p : privileges) {
			PrivilegeElementRow fRow = (PrivilegeElementRow) (functionId2FuntionNode.get(p.getFunction().getId()))
					.getData();
			fRow.setPrivilegeId(p.getId());
			fRow.updateMode(p.getPossibleMode(), true);
			functionId2Privilege.put(p.getFunction().getId(), p);
		}

		for (Module m : module2Functions.keySet()) {
			updateCheckModule(m, "C");
			updateCheckModule(m, "R");
			updateCheckModule(m, "U");
			updateCheckModule(m, "D");
			updateCheckModule(m, "E");
		}
	}

	/**
	 * When check a mode on the module, the mode of functions belong to that
	 * module also be checked.
	 * 
	 * @param isModule
	 * @param objectId
	 *            moduleId or functionId
	 * @param mode
	 *            C, R, U, D, E
	 * @param checked
	 */
	public void checkAction(boolean isModule, Long objectId, String mode, boolean checked) {
		if (isModule) {
			checkModule(objectId, mode, checked);
			Module m = getModule(objectId);
			for (Function function : module2Functions.get(m)) {
				setFunctionMode(mode, checked, function);
			}
		} else {
			Function function = functionId2Funtion.get(objectId);
			setFunctionMode(mode, checked, function);
			updateCheckModule(function.getModule(), mode);
		}
	}

	private void updateCheckModule(Module m, String mode) {
		for (Function f : module2Functions.get(m)) {
			DefaultTreeNode fNode = functionId2FuntionNode.get(f.getId());
			PrivilegeElementRow row = (PrivilegeElementRow) fNode.getData();
			if (!row.isChecked(mode)) {
				((PrivilegeElementRow) fNode.getParent().getData()).updateMode(mode, false);
				return;
			}
		}
		Long firstFunction = module2Functions.get(m).get(0).getId();
		DefaultTreeNode firstFunctionNode = functionId2FuntionNode.get(firstFunction);
		TreeNode moduleNode = firstFunctionNode.getParent();
		((PrivilegeElementRow) moduleNode.getData()).updateMode(mode, true);
	}

	private void checkModule(Long moduleId, String mode, boolean checked) {
		for (TreeNode mNode : root.getChildren()) {
			PrivilegeElementRow mRow = ((PrivilegeElementRow) mNode.getData());
			if (mRow.getObjectId().equals(moduleId)) {
				mRow.updateMode(mode, checked);
			}
		}
	}

	private void setFunctionMode(String mode, boolean checked, Function function) {
		if (function.getPossibleMode().contains(mode)) {
			Long functionId = function.getId();
			Privilege privilege = functionId2Privilege.get(functionId);
			if (privilege == null) {
				privilege = new Privilege();
				privilege.setFunction(function);
				privilege.setProfile(operatorProfile);
				privileges.add(privilege);
				functionId2Privilege.put(functionId, privilege);
			}
			String possibleMode = privilege.getPossibleMode();
			if (checked) {
				if (!possibleMode.contains(mode)) {
					possibleMode = possibleMode.concat(mode);
				}
			} else {
				possibleMode = possibleMode.replace(mode, "");
			}
			privilege.setPossibleMode(possibleMode);
			((PrivilegeElementRow) functionId2FuntionNode.get(functionId).getData()).updateMode(mode, checked);
		}
	}

	private Module getModule(Long moduleId) {
		for (Module m : module2Functions.keySet()) {
			if (m.getId().equals(moduleId)) {
				return m;
			}
		}
		return null;
	}

	public DefaultTreeNode getRoot() {
		return root;
	}

	public IOperatorService getOperatorService() {
		return operatorService;
	}

	public void setOperatorService(IOperatorService operatorService) {
		this.operatorService = operatorService;
	}

	public class PrivilegeElementRow implements Serializable {
		private static final long serialVersionUID = -909901960486541520L;
		private Long objectId; // Id of module or function
		private String objectName; // Name of module of function
		private Long privilegeId;
		private boolean isModule;

		private Boolean isCreate = null; // null means not show the checkbox
		private Boolean isRead = null;
		private Boolean isUpdate = null;
		private Boolean isDelete = null;
		private Boolean isExecute = null;

		public PrivilegeElementRow(Long objectId, String objectName, boolean isModule) {
			super();
			this.objectId = objectId;
			this.objectName = objectName;
			this.isModule = isModule;
			if (isModule) {
				isCreate = isRead = isUpdate = isDelete = isExecute = false;
			}
		}

		public void updateMode(String mode, boolean checked) {
			if (isCreate != null && mode.contains("C")) {
				isCreate = checked;
			}
			if (isRead != null && mode.contains("R")) {
				isRead = checked;
			}
			if (isUpdate != null && mode.contains("U")) {
				isUpdate = checked;
			}
			if (isDelete != null && mode.contains("D")) {
				isDelete = checked;
			}
			if (isExecute != null && mode.contains("E")) {
				isExecute = checked;
			}
		}

		/**
		 * Clear selected checkboxes, and set privilegeId = null
		 */
		public void resetRowData() {
			privilegeId = null;
			if (BooleanUtils.isTrue(isCreate)) {
				isCreate = false;
			}
			if (BooleanUtils.isTrue(isDelete)) {
				isDelete = false;
			}
			if (BooleanUtils.isTrue(isExecute)) {
				isExecute = false;
			}
			if (BooleanUtils.isTrue(isUpdate)) {
				isUpdate = false;
			}
			if (BooleanUtils.isTrue(isRead)) {
				isRead = false;
			}
		}

		public boolean isChecked(String mode) {
			if (mode.contains("C")) {
				return BooleanUtils.isTrue(isCreate);
			}
			if (mode.contains("R")) {
				return BooleanUtils.isTrue(isRead);
			}
			if (mode.contains("U")) {
				return BooleanUtils.isTrue(isUpdate);
			}
			if (mode.contains("D")) {
				return BooleanUtils.isTrue(isDelete);
			}
			if (mode.contains("E")) {
				return BooleanUtils.isTrue(isExecute);
			}
			return false;
		}

		public Long getObjectId() {
			return objectId;
		}

		public void setObjectId(Long objectId) {
			this.objectId = objectId;
		}

		public String getObjectName() {
			return objectName;
		}

		public void setObjectName(String objectName) {
			this.objectName = objectName;
		}

		public Long getPrivilegeId() {
			return privilegeId;
		}

		public void setPrivilegeId(Long privilegeId) {
			this.privilegeId = privilegeId;
		}

		public boolean isModule() {
			return isModule;
		}

		public void setModule(boolean isModule) {
			this.isModule = isModule;
		}

		public Boolean getCreate() {
			return isCreate;
		}

		public void setCreate(Boolean isCreate) {
			this.isCreate = isCreate;
		}

		public Boolean getRead() {
			return isRead;
		}

		public void setRead(Boolean isRead) {
			this.isRead = isRead;
		}

		public Boolean getUpdate() {
			return isUpdate;
		}

		public void setUpdate(Boolean isUpdate) {
			this.isUpdate = isUpdate;
		}

		public Boolean getDelete() {
			return isDelete;
		}

		public void setDelete(Boolean isDelete) {
			this.isDelete = isDelete;
		}

		public Boolean getExecute() {
			return isExecute;
		}

		public void setExecute(Boolean isExecute) {
			this.isExecute = isExecute;
		}

	}
}
